package com.colin.library.view;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.text.DynamicLayout;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.TextView;

import java.lang.reflect.Field;

@SuppressLint("AppCompatCustomView")
public class TextViewMore extends TextView {

    public static final int STATE_SHRINK = 0;
    public static final int STATE_EXPAND = 1;

    private static final String CLASS_NAME_VIEW = "android.view.View";
    private static final String CLASS_NAME_LISTENER_INFO = "android.view.View$ListenerInfo";
    private static final String ELLIPSIS_HINT = "";//替换最后显示的省略号
    private static final String GAP_CHAR = " ";
    private static final int MIN_LINES_ON_SHRINK = 2;//默认显示行数
    private static final int MAX_LINES_ON_SHRINK = 3;//默认显示行数
    private static final int TO_EXPAND_HINT_COLOR = 0xFF005DAE;
    private static final int TO_SHRINK_HINT_COLOR = 0xFF005DAE;
    private static final int TO_EXPAND_HINT_COLOR_BG_PRESSED = 0x55999999;
    private static final int TO_SHRINK_HINT_COLOR_BG_PRESSED = 0x55999999;
    private static final boolean TOGGLE_ENABLE = true;
    private static final boolean SHOW_TO_EXPAND_HINT = true;
    private static final boolean SHOW_TO_SHRINK_HINT = true;

    private String mEllipsisHint;
    private String mToExpandHint;
    private String mToShrinkHint;
    private String mGapChar = GAP_CHAR;
    private boolean mToggleEnable = TOGGLE_ENABLE;
    private boolean mShowToExpandHint = SHOW_TO_EXPAND_HINT;
    private boolean mShowToShrinkHint = SHOW_TO_SHRINK_HINT;
    private boolean mShouldShowEndWord = true;
    private int mMaxLinesOnShrink = MAX_LINES_ON_SHRINK;
    private int mMinLinesOnShrink = MIN_LINES_ON_SHRINK;
    private int mToExpandHintColor = TO_EXPAND_HINT_COLOR;
    private int mToShrinkHintColor = TO_SHRINK_HINT_COLOR;
    private int mToExpandHintColorBgPressed = TO_EXPAND_HINT_COLOR_BG_PRESSED;
    private int mToShrinkHintColorBgPressed = TO_SHRINK_HINT_COLOR_BG_PRESSED;
    private int mCurrState = STATE_SHRINK;
    private boolean mShowEndWord = false;
    //  used to add to the tail of modified text, the "shrink" and "expand" text
    private TouchableSpan mTouchableSpan;
    private BufferType mBufferType = BufferType.NORMAL;
    private TextPaint mTextPaint;
    private Layout mLayout;
    private int mTextLineCount = -1;
    private int mLayoutWidth = 0;
    private int mFutureTextViewWidth = 0;
    private boolean haveMore = false;//是否可以收起扩展 默认没有更多状态
    private CharSequence mOrigText;

    private MoreClickListener mMoreClickListener;
    private OnMoreListener mOnMoreListener;

    public TextViewMore(Context context) {
        this(context, null, 0);
    }

    public TextViewMore(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TextViewMore(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttr(context, attrs);
        initData();
    }

    private void initAttr(Context context, AttributeSet attrs) {
        if (attrs == null) {
            return;
        }
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TextViewMore);
        if (typedArray == null) {
            return;
        }
        int indexCount = typedArray.getIndexCount();
        for (int i = 0; i < indexCount; i++) {
            int attr = typedArray.getIndex(i);
            if (attr == R.styleable.TextViewMore_maxLinesOnExpand) {
                mMaxLinesOnShrink = typedArray.getInteger(attr, MAX_LINES_ON_SHRINK);
            } else if (attr == R.styleable.TextViewMore_minLinesOnShrink) {
                mMinLinesOnShrink = typedArray.getInteger(attr, MIN_LINES_ON_SHRINK);
            } else if (attr == R.styleable.TextViewMore_ellipsisHint) {
                mEllipsisHint = typedArray.getString(attr);
            } else if (attr == R.styleable.TextViewMore_toExpandText) {
                mToExpandHint = typedArray.getString(attr);
            } else if (attr == R.styleable.TextViewMore_toShrinkText) {
                mToShrinkHint = typedArray.getString(attr);
            } else if (attr == R.styleable.TextViewMore_enableToggle) {
                mToggleEnable = typedArray.getBoolean(attr, TOGGLE_ENABLE);
            } else if (attr == R.styleable.TextViewMore_shouldShowEndWord) {
                mShouldShowEndWord = typedArray.getBoolean(attr, true);
            } else if (attr == R.styleable.TextViewMore_toExpandTextShow) {
                mShowToExpandHint = typedArray.getBoolean(attr, SHOW_TO_EXPAND_HINT);
            } else if (attr == R.styleable.TextViewMore_toShrinkTextShow) {
                mShowToShrinkHint = typedArray.getBoolean(attr, SHOW_TO_SHRINK_HINT);
            } else if (attr == R.styleable.TextViewMore_toExpandTextColor) {
                mToExpandHintColor = typedArray.getInteger(attr, TO_EXPAND_HINT_COLOR);
            } else if (attr == R.styleable.TextViewMore_toShrinkTextColor) {
                mToShrinkHintColor = typedArray.getInteger(attr, TO_SHRINK_HINT_COLOR);
            } else if (attr == R.styleable.TextViewMore_toExpandTextColorBgPressed) {
                mToExpandHintColorBgPressed = typedArray.getInteger(attr, TO_EXPAND_HINT_COLOR_BG_PRESSED);
            } else if (attr == R.styleable.TextViewMore_toShrinkTextColorBgPressed) {
                mToShrinkHintColorBgPressed = typedArray.getInteger(attr, TO_SHRINK_HINT_COLOR_BG_PRESSED);
            } else if (attr == R.styleable.TextViewMore_initState) {
                mCurrState = typedArray.getInteger(attr, STATE_SHRINK);
            } else if (attr == R.styleable.TextViewMore_gapChar) {
                mGapChar = typedArray.getString(attr);
            }
        }
        typedArray.recycle();
    }

    private void initData() {
        mTouchableSpan = new TouchableSpan();
        setMovementMethod(new LinkTouchMovementMethod());

        if (TextUtils.isEmpty(mEllipsisHint)) {
            mEllipsisHint = ELLIPSIS_HINT;
        }
        //显示扩展的字符串
        if (mShowToExpandHint || !TextUtils.isEmpty(mToExpandHint)) {
            mShowToExpandHint = true;
            mToExpandHint = TextUtils.isEmpty(mToExpandHint) ? getResources().getString(R.string.to_expand_hint) : mToExpandHint;
        }
        //显示收起的字符串
        if (mShowToShrinkHint || !TextUtils.isEmpty(mToShrinkHint)) {
            mShowToShrinkHint = true;
            mToShrinkHint = TextUtils.isEmpty(mToShrinkHint) ? getResources().getString(R.string.to_shrink_hint) : mToShrinkHint;
        }

        if (mToggleEnable) {
            mMoreClickListener = new MoreClickListener();
            setOnClickListener(mMoreClickListener);
        }
        getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                ViewTreeObserver obs = getViewTreeObserver();
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    obs.removeOnGlobalLayoutListener(this);
                } else {
                    obs.removeGlobalOnLayoutListener(this);
                }
                setTextInternal(getNewTextByConfig(), mBufferType);
            }
        });
    }

    /**
     * used in ListView or RecyclerView to update ExpandableTextView
     *
     * @param text                original text
     * @param futureTextViewWidth the width of ExpandableTextView in px unit,
     *                            used to get max line number of original text by given the width
     * @param expandState         expand or shrink
     */
    public void updateForRecyclerView(CharSequence text, int futureTextViewWidth, int expandState) {
        mFutureTextViewWidth = futureTextViewWidth;
        mCurrState = expandState;
        setText(text);
    }

    public void updateForRecyclerView(CharSequence text, BufferType type, int futureTextViewWidth) {
        mFutureTextViewWidth = futureTextViewWidth;
        setText(text, type);
    }

    public void updateForRecyclerView(CharSequence text, int futureTextViewWidth) {
        mFutureTextViewWidth = futureTextViewWidth;
        setText(text);
    }

    /**
     * get the current state of ExpandableTextView
     *
     * @return STATE_SHRINK if in shrink state
     * STATE_EXPAND if in expand state
     */
    public int getExpandState() {
        return mCurrState;
    }

    /**
     * refresh and get a will-be-displayed text by current configuration
     *
     * @return get a will-be-displayed text
     */
    private CharSequence getNewTextByConfig() {
        String endWord = mCurrState == STATE_SHRINK ? mToExpandHint : mToShrinkHint;
        //空或者状态不对
        if (TextUtils.isEmpty(mOrigText) || (mCurrState == STATE_EXPAND && !mShowToShrinkHint) || (mCurrState == STATE_SHRINK && !mShowToExpandHint)) {
            return getMoreCharSequence(endWord);
        }
        //计算高度
        mLayout = getLayout();
        if (mLayout != null) {
            mLayoutWidth = mLayout.getWidth();
        }
        if (mLayoutWidth <= 0) {
            if (getWidth() == 0) {
                if (mFutureTextViewWidth == 0) {
                    return getMoreCharSequence(endWord);
                } else {
                    mLayoutWidth = mFutureTextViewWidth - getPaddingLeft() - getPaddingRight();
                }
            } else {
                mLayoutWidth = getWidth() - getPaddingLeft() - getPaddingRight();
            }
        }

        mTextPaint = getPaint();

        mTextLineCount = -1;
        //计算文本内容在控件中显示行数
        mLayout = new DynamicLayout(mOrigText, mTextPaint, mLayoutWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
        mTextLineCount = mLayout.getLineCount();

        //是否有更多，只需要收起状态的时候检查就行了
        if (mCurrState == STATE_SHRINK) {
            haveMore = mTextLineCount > mMinLinesOnShrink;
        }

        //收起状态小于最小
        if (mCurrState == STATE_SHRINK && mTextLineCount <= mMinLinesOnShrink) {
            return getMoreCharSequence(endWord);
        }
        //扩展状态小于最大
        if (mCurrState == STATE_EXPAND && mTextLineCount <= mMaxLinesOnShrink) {
            return getMoreCharSequence(endWord);
        }
        //需要计算
        return getMoreCharSequenceByMax(endWord);
    }

    private CharSequence getMoreCharSequenceByMax(String endWord) {
        int maxLineCount = mCurrState == STATE_EXPAND ? mMaxLinesOnShrink : mMinLinesOnShrink;

        int indexEnd = getValidLayout().getLineEnd(maxLineCount - 1);
        int indexStart = getValidLayout().getLineStart(maxLineCount - 1);
        int indexEndTrimmed = indexEnd
                - getLengthOfString(mEllipsisHint)
                - getLengthOfString(endWord) - getLengthOfString(mGapChar);

        if (indexEndTrimmed <= indexStart) {
            indexEndTrimmed = indexEnd;
        }

        int remainWidth = getValidLayout().getWidth() -
                (int) (mTextPaint.measureText(mOrigText.subSequence(indexStart, indexEndTrimmed).toString()) + 0.5);
        float widthTailReplaced = mTextPaint.measureText(getContentOfString(mEllipsisHint)
                + getContentOfString(endWord) + getContentOfString(mGapChar));

        int indexEndTrimmedRevised = indexEndTrimmed;
        if (remainWidth > widthTailReplaced) {
            int extraOffset = 0;
            int extraWidth = 0;
            while (remainWidth > widthTailReplaced + extraWidth) {
                extraOffset++;
                if (indexEndTrimmed + extraOffset <= mOrigText.length()) {
                    extraWidth = (int) (mTextPaint.measureText(
                            mOrigText.subSequence(indexEndTrimmed, indexEndTrimmed + extraOffset).toString()) + 0.5);
                } else {
                    break;
                }
            }
            indexEndTrimmedRevised += extraOffset - 1;
        } else {
            int extraOffset = 0;
            int extraWidth = 0;
            while (remainWidth + extraWidth < widthTailReplaced) {
                extraOffset--;
                if (indexEndTrimmed + extraOffset > indexStart) {
                    extraWidth = (int) (mTextPaint.measureText(mOrigText.subSequence(indexEndTrimmed + extraOffset, indexEndTrimmed).toString()) + 0.5);
                } else {
                    break;
                }
            }
            indexEndTrimmedRevised += extraOffset;
        }

        String fixText = removeEndLineBreak(mOrigText.subSequence(0, indexEndTrimmedRevised));
        SpannableStringBuilder ssbShrink = new SpannableStringBuilder(fixText)
                .append(mEllipsisHint);
        ssbShrink.append(getContentOfString(mGapChar) + getContentOfString(endWord));
        ssbShrink.setSpan(mTouchableSpan, ssbShrink.length() - getLengthOfString(endWord), ssbShrink.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        return ssbShrink;
    }

    /**
     * 最新值
     *
     * @param endWord
     * @return
     */
    private CharSequence getMoreCharSequence(String endWord) {
        endWord = mShouldShowEndWord || haveMore ? endWord : "";
        SpannableStringBuilder ssbShrink = null;
        if (TextUtils.isEmpty(endWord)) {
            ssbShrink = new SpannableStringBuilder(mOrigText);
        } else {
            ssbShrink = new SpannableStringBuilder(mOrigText).append(mGapChar).append(endWord);
            ssbShrink.setSpan(mTouchableSpan, ssbShrink.length() - getLengthOfString(endWord), ssbShrink.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
        return ssbShrink;
    }

    private String removeEndLineBreak(CharSequence text) {
        String str = text.toString();
        while (str.endsWith("\n")) {
            str = str.substring(0, str.length() - 1);
        }
        return str;
    }

    public void setOnMoreListener(OnMoreListener listener) {
        mOnMoreListener = listener;
    }

    private Layout getValidLayout() {
        return mLayout != null ? mLayout : getLayout();
    }

    private void toggle() {
        switch (mCurrState) {
            case STATE_SHRINK:
                mCurrState = STATE_EXPAND;
                if (mOnMoreListener != null) {
                    mOnMoreListener.onExpand(this);
                }
                break;
            case STATE_EXPAND:
                mCurrState = STATE_SHRINK;
                if (mOnMoreListener != null) {
                    mOnMoreListener.onShrink(this);
                }
                break;
        }
        setTextInternal(getNewTextByConfig(), mBufferType);

    }

    @Override
    public void setText(CharSequence text, BufferType type) {
        mOrigText = text;
        mBufferType = type;
        setTextInternal(getNewTextByConfig(), type);
    }

    public void setEndWord(String endWord) {
        mToShrinkHint = endWord;
        mToExpandHint = endWord;
        mShowEndWord = !TextUtils.isEmpty(endWord);
    }


    private void setTextInternal(CharSequence text, BufferType type) {
        super.setText(text, type);
    }

    private int getLengthOfString(String string) {
        if (string == null)
            return 0;
        return string.length();
    }

    private String getContentOfString(String string) {
        if (string == null)
            return "";
        return string;
    }

    public void setChangeState() {
        toggle();
    }

    public interface OnMoreListener {
        void onExpand(TextViewMore view);

        void onShrink(TextViewMore view);
    }

    private class MoreClickListener implements View.OnClickListener {
        @Override
        public void onClick(View view) {
            toggle();
        }
    }

    public View.OnClickListener getOnClickListener(View view) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            return getOnClickListenerV14(view);
        } else {
            return getOnClickListenerV(view);
        }
    }

    private View.OnClickListener getOnClickListenerV(View view) {
        View.OnClickListener retrievedListener = null;
        try {
            Field field = Class.forName(CLASS_NAME_VIEW).getDeclaredField("mOnClickListener");
            field.setAccessible(true);
            retrievedListener = (View.OnClickListener) field.get(view);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return retrievedListener;
    }

    private View.OnClickListener getOnClickListenerV14(View view) {
        View.OnClickListener retrievedListener = null;
        try {
            Field listenerField = Class.forName(CLASS_NAME_VIEW).getDeclaredField("mListenerInfo");
            Object listenerInfo = null;

            if (listenerField != null) {
                listenerField.setAccessible(true);
                listenerInfo = listenerField.get(view);
            }

            Field clickListenerField = Class.forName(CLASS_NAME_LISTENER_INFO).getDeclaredField("mOnClickListener");

            if (clickListenerField != null && listenerInfo != null) {
                clickListenerField.setAccessible(true);
                retrievedListener = (View.OnClickListener) clickListenerField.get(listenerInfo);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return retrievedListener;
    }


    /**
     * Copy from:
     * http://stackoverflow.com/questions
     * /20856105/change-the-text-color-of-a-single-clickablespan-when-pressed-without-affecting-o
     * By:
     * Steven Meliopoulos
     */
    private class TouchableSpan extends ClickableSpan {
        private boolean mIsPressed;

        public void setPressed(boolean isSelected) {
            mIsPressed = isSelected;
        }

        @Override
        public void onClick(View widget) {
            if (hasOnClickListeners() && (getOnClickListener(TextViewMore.this) instanceof MoreClickListener)) {
            } else {
                toggle();
            }
        }

        @Override
        public void updateDrawState(TextPaint ds) {
            super.updateDrawState(ds);
            switch (mCurrState) {
                case STATE_SHRINK:
                    ds.setColor(mToExpandHintColor);
                    ds.bgColor = mIsPressed ? mToExpandHintColorBgPressed : 0;
                    break;
                case STATE_EXPAND:
                    ds.setColor(mToShrinkHintColor);
                    ds.bgColor = mIsPressed ? mToShrinkHintColorBgPressed : 0;
                    break;
            }
            ds.setUnderlineText(false);
        }
    }

    /**
     * Copy from:
     * http://stackoverflow.com/questions
     * /20856105/change-the-text-color-of-a-single-clickablespan-when-pressed-without-affecting-o
     * By:
     * Steven Meliopoulos
     */
    public class LinkTouchMovementMethod extends LinkMovementMethod {
        private TouchableSpan mPressedSpan;

        @Override
        public boolean onTouchEvent(TextView textView, Spannable spannable, MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                mPressedSpan = getPressedSpan(textView, spannable, event);
                if (mPressedSpan != null) {
                    mPressedSpan.setPressed(true);
                    Selection.setSelection(spannable, spannable.getSpanStart(mPressedSpan),
                            spannable.getSpanEnd(mPressedSpan));
                }
            } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
                TouchableSpan touchedSpan = getPressedSpan(textView, spannable, event);
                if (mPressedSpan != null && touchedSpan != mPressedSpan) {
                    mPressedSpan.setPressed(false);
                    mPressedSpan = null;
                    Selection.removeSelection(spannable);
                }
            } else {
                if (mPressedSpan != null) {
                    mPressedSpan.setPressed(false);
                    super.onTouchEvent(textView, spannable, event);
                }
                mPressedSpan = null;
                Selection.removeSelection(spannable);
            }
            return true;
        }

        private TouchableSpan getPressedSpan(TextView textView, Spannable spannable, MotionEvent event) {

            int x = (int) event.getX();
            int y = (int) event.getY();

            x -= textView.getTotalPaddingLeft();
            y -= textView.getTotalPaddingTop();

            x += textView.getScrollX();
            y += textView.getScrollY();

            Layout layout = textView.getLayout();
            int line = layout.getLineForVertical(y);
            int off = layout.getOffsetForHorizontal(line, x);

            TouchableSpan[] link = spannable.getSpans(off, off, TouchableSpan.class);
            TouchableSpan touchedSpan = null;
            if (link.length > 0) {
                touchedSpan = link[0];
            }
            return touchedSpan;
        }
    }
}